package trackexplorer.wizard;

import java.beans.XMLDecoder;
import java.beans.XMLEncoder;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;

import org.apache.tools.tar.TarEntry;
import org.apache.tools.tar.TarInputStream;
import org.apache.tools.tar.TarOutputStream;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.preferences.ConfigurationScope;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.wizard.Wizard;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.DirectoryDialog;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.INewWizard;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.PlatformUI;
import org.osgi.service.prefs.BackingStoreException;
import org.osgi.service.prefs.Preferences;

import trackexplorer.Activator;
import trackexplorer.Calibration;
import trackexplorer.editors.MapViewer;

public class AtlasExportWizard extends Wizard implements INewWizard{

    public static final String              ID     = "trackexplorer.wizard.AtlasExportWizard";
    AtlasPage                               atlasPage;
    LayersPage                              carPage;
    private String                          atlas;
    private Map<String, LinkedList<String>> layers = new HashMap<String, LinkedList<String>>();
    private boolean                         tar;
    private Display                         display;

    @Override
    public void addPages(){
        atlasPage = new AtlasPage();
        addPage(atlasPage);
        carPage = new LayersPage();
        addPage(carPage);
    }

    public void init(IWorkbench workbench, IStructuredSelection selection){
        display = workbench.getActiveWorkbenchWindow().getShell().getDisplay();
    }

    @Override
    public boolean canFinish(){
        return atlas != null;
    }

    @Override
    public boolean performFinish(){
        final Preferences prefs = new ConfigurationScope()
            .getNode(MapViewer.ID);
        DirectoryDialog dialog = new DirectoryDialog(PlatformUI.getWorkbench()
            .getActiveWorkbenchWindow().getShell(), SWT.SAVE);
        dialog.setFilterPath(prefs.get("wizarddir", "/"));
        final String open = dialog.open();
        if(open == null){
            return false;
        }

        IRunnableWithProgress op = new IRunnableWithProgress(){
            public void run(IProgressMonitor monitor)
                throws InvocationTargetException, InterruptedException{
                File file = new File(open);
                prefs.put("wizarddir", file.getAbsolutePath());
                try{
                    prefs.flush();
                }catch(BackingStoreException e){
                    e.printStackTrace();
                }
                try{
                    if(!createAtlas(file))
                        throw new InterruptedException();
                }catch(Exception e){
                    throw new InterruptedException();
                }
            }
        };
        try{
            new ProgressMonitorDialog(PlatformUI.getWorkbench()
                .getActiveWorkbenchWindow().getShell()).run(true, true, op);
            return true;
        }catch(Exception e){
            e.printStackTrace();
            return false;
        }
    }

    private boolean createAtlas(File file) throws IOException{
        File atlas = new File(file, this.atlas);
        atlas.mkdirs();
        File tba = new File(atlas, this.atlas + ".tba");
        FileWriter fw = new FileWriter(tba);
        fw.write("Atlas 1.0");
        fw.close();
        for(String lr: getLayers()){
            File layer = new File(atlas, lr);
            layer.mkdir();
            for(String map: layers.get(lr)){
                if(map.toLowerCase().endsWith(".map")){
                    File mapdir = new File(map).getParentFile();
                    File dstPath = new File(layer, mapdir.getName());
                    File set = new File(mapdir, "set");
                    if(!(set.exists() && set.isDirectory())){
                        File[] imgFiles = mapdir.listFiles(new FileFilter(){
                            public boolean accept(File pathname){
                                return Calibration.isGraphics(pathname);
                            }
                        });
                        if(!(imgFiles == null || imgFiles.length < 1)){
                            Image img = new Image(display, new FileInputStream(
                                imgFiles[0]));
                            Activator.saveTBmap(dstPath, new File(map), img);
                            img.dispose();
                            img = null;
                        }
                    }else{
                        if(!copyDirectory(mapdir, dstPath)){
                            return false;
                        }
                    }
                }else if(map.toLowerCase().endsWith(".tar")){
                    String name = new File(map).getName();
                    File mapdir = new File(layer, name.substring(0, name
                        .length() - 4));
                    if(!unpack(map, mapdir)){
                        return false;
                    }
                }
            }
        }
        if(tar){
            pack(atlas);
            tba.delete();
            for(File layer: atlas.listFiles()){
                if(layer.isDirectory()){
                    for(File map: layer.listFiles()){
                        if(map.isDirectory()){
                            packMap(map);
                        }
                    }
                }
            }
        }
        return true;
    }

    private void packMap(File map) throws IOException{
        File tarFile = new File(map, map.getName() + ".tar");
        FileOutputStream fos = new FileOutputStream(tarFile);
        TarOutputStream tar = new TarOutputStream(fos);
        packDir(tar, map, map.getAbsolutePath().length() + 1, true);
        tar.close();
        emptyDir(map);
    }

    private void emptyDir(File file){
        if(file.getName().toLowerCase().endsWith(".tar")){
            return;
        }
        if(file.isDirectory()){
            for(File f: file.listFiles()){
                emptyDir(f);
            }
        }
        file.delete();
    }

    private void pack(File atlas) throws IOException{
        File tarFile = new File(atlas, atlas.getName() + ".tar");
        FileOutputStream fos = new FileOutputStream(tarFile);
        TarOutputStream tar = new TarOutputStream(fos);
        packDir(tar, atlas, atlas.getAbsolutePath().length() + 1, false);
        tar.close();
    }

    private void packDir(TarOutputStream tar, File dir2pack, int pathLen,
        boolean packSet) throws IOException{
        if(dir2pack.isDirectory()){
            if(!packSet && "set".equals(dir2pack.getName())){
                TarEntry entry = new TarEntry(dir2pack);
                String name = dir2pack.getAbsolutePath().substring(pathLen)
                    .replaceAll("\\\\", "/");
                entry.setName(name);
                tar.putNextEntry(entry);
                tar.closeEntry();
                return;
            }
            for(File f: dir2pack.listFiles()){
                packDir(tar, f, pathLen, packSet);
            }
            return;
        }
        if(!acceptFile(dir2pack)){
            return;
        }
        byte[] buf = new byte[2 * 1024];
        FileInputStream in = new FileInputStream(dir2pack);
        TarEntry entry = new TarEntry(dir2pack);
        String name = dir2pack.getAbsolutePath().substring(pathLen).replaceAll(
            "\\\\", "/");
        entry.setName(name);
        tar.putNextEntry(entry);
        int len;
        while((len = in.read(buf)) > 0){
            tar.write(buf, 0, len);
        }
        tar.closeEntry();
        in.close();
    }

    private boolean acceptFile(File file){
        String name = file.getName().toLowerCase();
        if(Calibration.isGraphics(name)){
            return true;
        }
        if(name.endsWith(".map")){
            return true;
        }
        if(name.endsWith(".tba")){
            return true;
        }
        return false;
    }

    private boolean unpack(String file, File mapdir) throws IOException{
        mapdir.mkdirs();
        TarInputStream tar = new TarInputStream(new FileInputStream(file));
        TarEntry entry;
        while((entry = tar.getNextEntry()) != null){
            File f = new File(mapdir, entry.getName());
            f.getParentFile().mkdirs();
            f.createNewFile();
            if(entry.isDirectory()){
                f.mkdir();
                continue;
            }
            FileOutputStream fos = new FileOutputStream(f);
            tar.copyEntryContents(fos);
            fos.close();
        }
        tar.close();
        return true;
    }

    private boolean copyDirectory(File srcPath, File dstPath)
        throws IOException{
        if(srcPath.isDirectory()){
            if(!dstPath.exists()){
                dstPath.mkdir();
            }
            String files[] = srcPath.list();
            for(int i = 0; i < files.length; i++){
                if(!copyDirectory(new File(srcPath, files[i]), new File(
                    dstPath, files[i]))){
                    return false;
                }
            }
        }else{
            if(!srcPath.exists()){
                return false;
            }else{
                InputStream in = new FileInputStream(srcPath);
                OutputStream out = new FileOutputStream(dstPath);
                byte[] buf = new byte[1024];
                int len;
                while((len = in.read(buf)) > 0){
                    out.write(buf, 0, len);
                }
                in.close();
                out.close();
            }
        }
        return true;
    }

    public void setAtlas(String text){
        atlas = text;
    }

    public String getAtlas(){
        return atlas;
    }

    public void addLayer(String layer){
        layers.put(layer, new LinkedList<String>());
    }

    public Set<String> getLayers(){
        return layers.keySet();
    }

    public void addMap(String layer, String map){
        layers.get(layer).add(map);
    }

    public LinkedList<String> getMaps(String layer){
        return layers.get(layer);
    }

    public void setTar(boolean b){
        tar = b;
    }

    public boolean getTar(){
        return tar;
    }

    public void save(File file) throws IOException{
        XMLEncoder encoder = new XMLEncoder(new FileOutputStream(file));
        encoder.writeObject(atlas);
        encoder.writeObject(layers);
        encoder.writeObject(tar);
        encoder.close();
    }

    @SuppressWarnings("unchecked")
    public void load(File file) throws IOException{
        XMLDecoder decoder = new XMLDecoder(new FileInputStream(file));
        atlas = (String)decoder.readObject();
        layers = (Map<String, LinkedList<String>>)decoder.readObject();
        tar = (Boolean)decoder.readObject();
        decoder.close();
    }
}
